//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.IO;
using EffiProz.Core.Lib;
using EffiProz.Core.Errors;
using EffiProz.Core.Lib.IO;

namespace EffiProz.Core.Persist
{

    // fredt@users 20030111 - patch 1.7.2 by bohgammer@users - pad file before seek() beyond end
    // some incompatible JVM implementations do not allow seek beyond the existing end of file

    /**
     * This class is a wapper for a random access file such as that used for
     * CACHED table storage.
     *
     * @author fredt@users
     * @version  1.8.0
     * @since  1.7.2
     */
    public class ScaledRAFile : ScaledRAInterface
    {

        public const int DATA_FILE_RAF = 0;
        public const int DATA_FILE_NIO = 1;

        public const long MAX_NIO_LENGTH = (1L << 28);

        //
        Database database;
        public FileStream file;
        public bool _readOnly;
        public string fileName;
        public bool isNio;
        public bool bufferDirty = true;
        public byte[] buffer;
        public ByteArrayInputStream ba;
        public long bufferOffset;

        //
        public long seekPosition;
        public long realPosition;
        public static int cacheHit;

        /**
         * seekPosition is the position _in seek() calls or after reading or writing
         * realPosition is the file position
         */
        public static Storage newScaledRAFile(Database database, string name,
                                       bool _readonly, int type)
        {


            if (type == DATA_FILE_RAF)
            {
                return new ScaledRAFile(database, name, _readonly);
            }
            else
            {
                FileStream file = FileUtil.GetDefaultInstance().OpenOutputStreamElement(name, FileMode.OpenOrCreate, _readonly ? FileAccess.Read :
                    FileAccess.ReadWrite);

                if (file.Length > MAX_NIO_LENGTH)
                {
                    return new ScaledRAFile(database, name, file, _readonly);
                }
                else
                {
                    file.Close();
                }

                //try //TODOx
                //{
                //    return new ScaledRAFileHybrid(database, name, _readonly);

                //}
                //catch (Exception )
                //{
                    return new ScaledRAFile(database, name, _readonly);
                //}
            }
        }

        public ScaledRAFile(Database database, string name, FileStream file,
                     bool _readonly)
        {

            this.database = database;
            this._readOnly = _readonly;
            this.fileName = name;
            this.file = file;

            int bufferSize = 1 << 12;

            buffer = new byte[bufferSize];
            ba = new ByteArrayInputStream(buffer);
        }

        public ScaledRAFile(Database database, string name,
                      bool _readonly)
        {

            this.database = database;
            this._readOnly = _readonly;
            this.fileName = name;
            this.file = FileUtil.GetDefaultInstance().OpenOutputStreamElement(name, FileMode.OpenOrCreate, _readonly ? FileAccess.Read :
                    FileAccess.ReadWrite);

            int bufferSize = 1 << 12;

            buffer = new byte[bufferSize];
            ba = new ByteArrayInputStream(buffer);
        }

        public long length()
        {
            return file.Length;
        }

        /**
         * Some JVM's do not allow seek beyond end of file, so zeros are written
         * first _in that case. Reported by bohgammer@users _in Open Disucssion
         * Forum.
         */
        public void seek(long position)
        {

            if (!_readOnly && file.Length < position)
            {
                long tempSize = position - file.Length;

                if (tempSize > 1 << 18)
                {
                    tempSize = 1 << 18;
                }

                byte[] temp = new byte[(int)tempSize];

                try
                {
                    long pos = file.Length;

                    for (; pos < position - tempSize; pos += tempSize)
                    {
                        file.Seek(pos, SeekOrigin.Begin);
                        file.Write(temp, 0, (int)tempSize);
                    }

                    file.Seek(pos, SeekOrigin.Begin);
                    file.Write(temp, 0, (int)(position - pos));

                    realPosition = position;
                }
                catch (IOException e)
                {
                    database.logger.logWarningEvent("seek failed", e);

                    throw e;
                }
            }

            seekPosition = position;
        }

        public long getFilePointer()
        {
            return seekPosition;
        }

        private void readIntoBuffer()
        {

            long filePos = seekPosition;
            long subOffset = filePos % buffer.Length;
            long fileLength = file.Length;
            long readLength = fileLength - (filePos - subOffset);

            try
            {
                if (readLength <= 0)
                {
                    throw new IOException("read beyond end of file");
                }

                if (readLength > buffer.Length)
                {
                    readLength = buffer.Length;
                }

                file.Seek(filePos - subOffset, SeekOrigin.Begin);
                file.Read(buffer, 0, (int)readLength);

                bufferOffset = filePos - subOffset;
                realPosition = bufferOffset + readLength;
                bufferDirty = false;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent(" " + realPosition + " "
                                           + readLength, e);

                throw e;
            }
        }

        public int read()
        {

            try
            {
                long fileLength = file.Length;

                if (seekPosition >= fileLength)
                {
                    return -1;
                }

                if (bufferDirty || seekPosition < bufferOffset
                        || seekPosition >= bufferOffset + buffer.Length)
                {
                    readIntoBuffer();
                }
                else
                {
                    cacheHit++;
                }

                ba.reset();
                ba.skip(seekPosition - bufferOffset);

                int val = ba.read();

                seekPosition++;

                return val;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("read failed", e);

                throw e;
            }
        }

        public long readLong()
        {

            try
            {
                if (bufferDirty || seekPosition < bufferOffset
                        || seekPosition >= bufferOffset + buffer.Length)
                {
                    readIntoBuffer();
                }
                else
                {
                    cacheHit++;
                }

                ba.reset();

                if (seekPosition - bufferOffset
                        != ba.skip(seekPosition - bufferOffset))
                {
                    throw new InvalidOperationException();
                }

                long val=0;

                try
                {
                    val = ba.readLong();
                }
                catch (InvalidOperationException )
                {
                    file.Seek(seekPosition, SeekOrigin.Begin);

                    byte[] temp = new byte[8];
                    file.Read(temp, 0, 8);
                    val = BitConverter.ToInt64(temp, 0);
                    realPosition = file.Position;
                }

                seekPosition += 8;

                return val;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("failed ot read a Long", e);

                throw e;
            }
        }

        public int readInt()
        {

            try
            {
                if (bufferDirty || seekPosition < bufferOffset
                        || seekPosition >= bufferOffset + buffer.Length)
                {
                    readIntoBuffer();
                }
                else
                {
                    cacheHit++;
                }

                ba.reset();

                if (seekPosition - bufferOffset
                        != ba.skip(seekPosition - bufferOffset))
                {
                    throw new InvalidOperationException();
                }

                int val=0;

                try
                {
                    val = ba.readInt();
                }
                catch (InvalidOperationException)
                {
                    file.Seek(seekPosition, SeekOrigin.Begin);

                    byte[] temp = new byte[4];
                    file.Read(temp, 0, 4);
                    val = BitConverter.ToInt32(temp, 0);
                    realPosition = file.Position;
                }

                seekPosition += 4;

                return val;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("failed to read an Int", e);

                throw e;
            }
        }

        public void read(byte[] b, int offset, int length)
        {

            try
            {
                if (bufferDirty || seekPosition < bufferOffset
                        || seekPosition >= bufferOffset + buffer.Length)
                {
                    readIntoBuffer();
                }
                else
                {
                    cacheHit++;
                }

                ba.reset();

                if (seekPosition - bufferOffset
                        != ba.skip(seekPosition - bufferOffset))
                {
                    throw new InvalidOperationException();
                }

                int bytesRead = ba.read(b, offset, length);

                seekPosition += bytesRead;

                if (bytesRead < length)
                {
                    if (seekPosition != realPosition)
                    {
                        file.Seek(seekPosition, SeekOrigin.Begin);
                    }

                    file.Read(b, offset + bytesRead, length - bytesRead);

                    seekPosition += (length - bytesRead);
                    realPosition = seekPosition;
                }
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("faeild to read a byte array", e);

                throw e;
            }
        }

        public void write(byte[] b, int off, int len)
        {

            try
            {
                if (realPosition != seekPosition)
                {
                    file.Seek(seekPosition, SeekOrigin.Begin);

                    realPosition = seekPosition;
                }

                if (seekPosition < bufferOffset + buffer.Length
                    && seekPosition + len > bufferOffset)
                {
                    bufferDirty = true;
                }

                file.Write(b, off, len);

                seekPosition += len;
                realPosition = seekPosition;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("failed to write a byte array", e);

                throw e;
            }
        }

        public void writeInt(int i)
        {

            try
            {
                if (realPosition != seekPosition)
                {
                    file.Seek(seekPosition, SeekOrigin.Begin);

                    realPosition = seekPosition;
                }
                
                if (seekPosition < bufferOffset + buffer.Length
                                    && seekPosition + 4 > bufferOffset)
                {
                    bufferDirty = true;
                }

                byte[] temp = BitConverter.GetBytes((int)i);
                file.Write(temp, 0, 4);

                seekPosition += 4;
                realPosition = seekPosition;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("failed to write an int", e);

                throw e;
            }
        }
        

        public void writeLong(long i)
        {

            try
            {
                if (realPosition != seekPosition)
                {
                    file.Seek(seekPosition, SeekOrigin.Begin);

                    realPosition = seekPosition;
                }
                

                if (seekPosition < bufferOffset + buffer.Length
                     && seekPosition + 8 > bufferOffset)
                {
                    bufferDirty = true;
                }

                byte[] temp = BitConverter.GetBytes(i);
                file.Write(temp, 0, 8);

                seekPosition += 8;
                realPosition = seekPosition;
            }
            catch (IOException e)
            {
                resetPointer();
                database.logger.logWarningEvent("failed to write a Long", e);

                throw e;
            }
        }

        public void close()
        {
            Error.printSystemOut("cache hit " + cacheHit);
            file.Close();
        }

        public bool isReadOnly()
        {
            return _readOnly;
        }

        public bool wasNio()
        {
            return false;
        }

        public bool canAccess(int length)
        {
            return true;
        }

        public bool canSeek(long position)
        {
            return true;
        }

        public Database getDatabase()
        {
            return null;
        }

        public void synch()
        {

            try
            {
                file.Flush();
            }
            catch (IOException ) { }
        }

        private void resetPointer()
        {

            try
            {
                bufferDirty = true;

                file.Seek(seekPosition, SeekOrigin.Begin);

                realPosition = seekPosition;
            }
            catch (Exception ) { }
        }
    }
}
